// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/audio/audio_output_dispatcher_impl.h"

#include <algorithm>

#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/single_thread_task_runner.h"
#include "base/time/time.h"
#include "media/audio/audio_output_proxy.h"

namespace media {

AudioOutputDispatcherImpl::AudioOutputDispatcherImpl(
    AudioManager* audio_manager,
    const AudioParameters& params,
    const std::string& output_device_id,
    const base::TimeDelta& close_delay)
    : AudioOutputDispatcher(audio_manager, params, output_device_id)
    , idle_proxies_(0)
    , close_timer_(FROM_HERE,
          close_delay,
          this,
          &AudioOutputDispatcherImpl::CloseAllIdleStreams)
    , audio_log_(
          audio_manager->CreateAudioLog(AudioLogFactory::AUDIO_OUTPUT_STREAM))
    , audio_stream_id_(0)
    , weak_factory_(this)
{
}

AudioOutputDispatcherImpl::~AudioOutputDispatcherImpl()
{
    CHECK(task_runner_->BelongsToCurrentThread());

    // Stop all active streams.
    for (auto& iter : proxy_to_physical_map_) {
        StopPhysicalStream(iter.second);
    }

    // Close all idle streams immediately.  The |close_timer_| will handle
    // invalidating any outstanding tasks upon its destruction.
    CloseAllIdleStreams();

    // All idle physical streams must have been closed during shutdown.
    CHECK(idle_streams_.empty());
}

AudioOutputProxy* AudioOutputDispatcherImpl::CreateStreamProxy()
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    return new AudioOutputProxy(weak_factory_.GetWeakPtr());
}

bool AudioOutputDispatcherImpl::OpenStream()
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    // Ensure that there is at least one open stream.
    if (idle_streams_.empty() && !CreateAndOpenStream())
        return false;

    ++idle_proxies_;
    close_timer_.Reset();
    return true;
}

bool AudioOutputDispatcherImpl::StartStream(
    AudioOutputStream::AudioSourceCallback* callback,
    AudioOutputProxy* stream_proxy)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    DCHECK(proxy_to_physical_map_.find(stream_proxy) == proxy_to_physical_map_.end());

    if (idle_streams_.empty() && !CreateAndOpenStream())
        return false;

    AudioOutputStream* physical_stream = idle_streams_.back();
    idle_streams_.pop_back();

    DCHECK_GT(idle_proxies_, 0u);
    --idle_proxies_;

    double volume = 0;
    stream_proxy->GetVolume(&volume);
    physical_stream->SetVolume(volume);
    const int stream_id = audio_stream_ids_[physical_stream];
    audio_log_->OnSetVolume(stream_id, volume);
    physical_stream->Start(callback);
    audio_log_->OnStarted(stream_id);
    proxy_to_physical_map_[stream_proxy] = physical_stream;

    close_timer_.Reset();
    return true;
}

void AudioOutputDispatcherImpl::StopStream(AudioOutputProxy* stream_proxy)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
    DCHECK(it != proxy_to_physical_map_.end());
    StopPhysicalStream(it->second);
    proxy_to_physical_map_.erase(it);
    ++idle_proxies_;
}

void AudioOutputDispatcherImpl::StreamVolumeSet(AudioOutputProxy* stream_proxy,
    double volume)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    AudioStreamMap::iterator it = proxy_to_physical_map_.find(stream_proxy);
    if (it != proxy_to_physical_map_.end()) {
        AudioOutputStream* physical_stream = it->second;
        physical_stream->SetVolume(volume);
        audio_log_->OnSetVolume(audio_stream_ids_[physical_stream], volume);
    }
}

void AudioOutputDispatcherImpl::CloseStream(AudioOutputProxy* stream_proxy)
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    DCHECK_GT(idle_proxies_, 0u);
    --idle_proxies_;

    // Leave at least a single stream running until the close timer fires to help
    // cycle time when streams are opened and closed repeatedly.
    CloseIdleStreams(std::max(idle_proxies_, static_cast<size_t>(1)));
    close_timer_.Reset();
}

bool AudioOutputDispatcherImpl::HasOutputProxies() const
{
    return idle_proxies_ || !proxy_to_physical_map_.empty();
}

bool AudioOutputDispatcherImpl::CreateAndOpenStream()
{
    DCHECK(task_runner_->BelongsToCurrentThread());

    const int stream_id = audio_stream_id_++;
    AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(
        params_, device_id_,
        base::Bind(&AudioLog::OnLogMessage, base::Unretained(audio_log_.get()),
            stream_id));
    if (!stream)
        return false;

    if (!stream->Open()) {
        stream->Close();
        return false;
    }

    audio_stream_ids_[stream] = stream_id;
    audio_log_->OnCreated(
        stream_id, params_, device_id_);

    idle_streams_.push_back(stream);
    return true;
}

void AudioOutputDispatcherImpl::CloseAllIdleStreams()
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    CloseIdleStreams(0);
}

void AudioOutputDispatcherImpl::CloseIdleStreams(size_t keep_alive)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    if (idle_streams_.size() <= keep_alive)
        return;
    for (size_t i = keep_alive; i < idle_streams_.size(); ++i) {
        AudioOutputStream* stream = idle_streams_[i];
        stream->Close();

        AudioStreamIDMap::iterator it = audio_stream_ids_.find(stream);
        DCHECK(it != audio_stream_ids_.end());
        audio_log_->OnClosed(it->second);
        audio_stream_ids_.erase(it);
    }
    idle_streams_.erase(idle_streams_.begin() + keep_alive, idle_streams_.end());
}

void AudioOutputDispatcherImpl::StopPhysicalStream(AudioOutputStream* stream)
{
    DCHECK(task_runner_->BelongsToCurrentThread());
    stream->Stop();
    audio_log_->OnStopped(audio_stream_ids_[stream]);
    idle_streams_.push_back(stream);
    close_timer_.Reset();
}

} // namespace media
